package kr.ac.kaist.savehearts.auth;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;

import kr.ac.kaist.savehearts.SaveHeartsConstants;

import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;

import com.facebook.android.AsyncFacebookRunner;
import com.facebook.android.AsyncFacebookRunner.RequestListener;
import com.facebook.android.Facebook;
import com.facebook.android.Facebook.DialogListener;
import com.facebook.android.FacebookError;
import com.facebook.android.FbDialogError;
import com.facebook.android.FbUtil;

/**
 * FacebookConnection provides authentication process through Facebook account.
 * It is implemented examples codes provided by facebook developers' page.
 * For more information, please refer to facebook developers' page (http://www.facebook.com/developers)
 * @author Seungwook Han (hsw1120@gmail.com)
 */

public class FacebookConnection {
	
	/**
	 * A tag for debugging
	 */
	public static final String TAG = FacebookConnection.class.getSimpleName();
	
	/**
	 * Application ID which is registered in developers' page in Facebook
	 */
	public static final String APP_ID = SaveHeartsConstants.FACEBOOK_APP_ID;
	
	/**
	 * AppSecret which is generated in developers' page in Facebook
	 */
	public static final String APP_SECRET = SaveHeartsConstants.FACEBOOK_APP_SECRET;
	
	/**
	 * A static variable and function to use FacebookConnection as a singleton
	 */
	private static FacebookConnection _instance;
	public static FacebookConnection getInstance(Activity activity, FacebookLoginListener listener) {
		if ( _instance == null ) _instance = new FacebookConnection(activity, listener);
		return _instance;
	}
	
	private Facebook mFacebook;
	public Facebook getFacebookInstance() { return mFacebook; }
	
	/**
	 * SharedPreferences to load/store user information
	 */
	private SharedPreferences mPrefs;
	
	/**
	 * AsyncFacebookRunner object to execute requests to retrieve user information
	 */
	private AsyncFacebookRunner mAsyncRunner;
	public AsyncFacebookRunner getAsyncRunner() { return mAsyncRunner; }
	
	private static final String[] PERMS = new String[] { "email", "offline_access" };

	private Context mContext;
	public Context getContext() { return mContext; }
	
	private Activity mActivity;
	public Activity getActivity() { return mActivity; }
	
	/**
	 * Is the user already logged in?
	 */
	private boolean bLoggedIn = false;
	public boolean isAuthenticated() { return bLoggedIn; }
	
	private FacebookLoginListener mListener;	
	
	/**
	 * Is the profile updated recently?
	 */
	private boolean bProfileUpdated = false;
	public boolean isProfileUpdated() { return bProfileUpdated; }
	
	/**
	 * last time when user profile is updated
	 */
	private long lastProfileUpdateTime = 0;
	public long getLastProfileUpdateTime() { return lastProfileUpdateTime; } 
	
	private UserInfo userInfo;
	public UserInfo getUserInfo() { return this.userInfo; }
	
	public FacebookConnection(Activity activity, FacebookLoginListener listener) {
		mFacebook = new Facebook(APP_ID);
		mAsyncRunner = new AsyncFacebookRunner(mFacebook);
		mActivity = activity;
		mContext = activity.getBaseContext();		
		mListener = listener;
		
		mPrefs = mActivity.getPreferences(activity.MODE_PRIVATE);
		
		initFacebookConnection();
	}
	
	private void initFacebookConnection() {
		bLoggedIn = false;
		bProfileUpdated = false;
		lastProfileUpdateTime = 0;
		this.userInfo = new UserInfo();
	}
		
	public void ReleaseFacebookConnection() {
		logoutFacebook();
	}
	
	public boolean updateFacebookConnection() {		
		if ( !bLoggedIn ) return false;
		if ( !updateUserProfile() ) return false;
		return true;
	}
	
	public boolean isUpdateFinished() {
		return bLoggedIn & bProfileUpdated;
	}
	
	
	public void loginFacebook() {
		if ( mActivity == null || mContext == null ) return;		
		mFacebook.authorize(mActivity, PERMS, new LoginDialogListener());
	}
	
	private class LoginDialogListener implements DialogListener {		
		@Override
		public void onCancel() { bLoggedIn = false;	}
		@Override
		public void onComplete(Bundle values) { 
			SharedPreferences.Editor editor = mPrefs.edit();
			editor.putString("access_token", mFacebook.getAccessToken());
			editor.putLong("access_expires", mFacebook.getAccessExpires());
			editor.commit();
			bLoggedIn = true; 
		}
		@Override
		public void onError(FbDialogError e) { bLoggedIn = false; e.printStackTrace(); }
		@Override
		public void onFacebookError(FacebookError e) { bLoggedIn = false; e.printStackTrace(); } 
	}
	
	private void logoutFacebook() {
		AsyncFacebookRunner asyncRunner = new AsyncFacebookRunner(mFacebook);
		asyncRunner.logout(mContext, new LogoutRequestListener());		
	}
	
	private class LogoutRequestListener implements RequestListener {		
		@Override
		public void onComplete(String response, Object state) { initFacebookConnection();	bLoggedIn = false; }
		@Override
		public void onFacebookError(FacebookError e, Object state) { e.printStackTrace(); }
		@Override
		public void onFileNotFoundException(FileNotFoundException e, Object state) { e.printStackTrace(); }
		@Override
		public void onIOException(IOException e, Object state) { e.printStackTrace(); }
		@Override
		public void onMalformedURLException(MalformedURLException e, Object state) { e.printStackTrace(); } 
	}
	
	private boolean updateUserProfile() {
		if ( !bLoggedIn || this.mAsyncRunner == null ) return false;		
		mAsyncRunner.request("me", new ProfileRequestListener());		
		return true;
	}
	
	private class ProfileRequestListener implements RequestListener {		 
		@Override
		public void onComplete(String response, Object state) {
			try {
				// process the response here: executed in background thread
				Log.d(TAG, "Response: " + response.toString());
				JSONObject json = FbUtil.parseJson(response);
				getUserInfo().setId(json.getString("id"));
				getUserInfo().setFullName(json.getString("name"));
				getUserInfo().setFirstName(json.getString("first_name"));
				getUserInfo().setLastName(json.getString("last_name"));
				getUserInfo().setEmail(json.getString("email"));
				
				String url = json.getString("link");
				url = "http://graph.facebook.com" + url.substring(url.lastIndexOf("/")) + "picture";
//				System.err.println(url);
				
				bProfileUpdated = true;
				lastProfileUpdateTime = System.currentTimeMillis();
			} catch (JSONException e) {
				Log.w(TAG, "JSON Error in response");
				Log.w(TAG, e.getMessage());
			} catch (FacebookError e) {
				Log.w(TAG, "Facebook Error: " + e.getMessage());
			} finally {
				if ( mListener != null )
					mListener.FacebookLoginCompleted(isUpdateFinished());
			}
		} 
		@Override
		public void onIOException(IOException e, Object state) { e.printStackTrace(); } 
		@Override
		public void onFileNotFoundException(FileNotFoundException e, Object state) { e.printStackTrace(); } 
		@Override
		public void onMalformedURLException(MalformedURLException e, Object state) { e.printStackTrace(); } 
		@Override
		public void onFacebookError(FacebookError e, Object state) { e.printStackTrace(); } 
	}
}
